﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;

namespace Haimen.Excel;

public class LargeExcelReader(
    ExcelFileUtils excelUtil) {

  public LargeExcelBook Read(string path) {
    FileStream stream = _excelUtil.OpenRead(path);
    SpreadsheetDocument doc = SpreadsheetDocument.Open(stream, isEditable: false);

    WorkbookPart? bookPart = doc.WorkbookPart;
    Debug.Assert(bookPart != null);

    Dictionary<int, SheetMeta> sheetMeta = ReadSheetMeta(bookPart);
    //foreach (var meta in sheetMeta) {
    //  Console.WriteLine($"sheet: {meta.Key} {meta.Value}");
    //}

    BookRes res = new(doc, stream);
    return new BookImpl(this, res, sheetMeta);
  }

  record SheetMeta {
    public string Id = "<NULL>";
    public string Name = "<NULL>";
  }

  Dictionary<int, SheetMeta> ReadSheetMeta(WorkbookPart bookPart) {
    Dictionary<int, SheetMeta> result = [];

    using OpenXmlReader reader = OpenXmlReader.Create(bookPart);
    int curPos = 1;

    while (reader.Read()) {
      if (reader.IsEndElement) {
        continue;
      }

      //Console.WriteLine(reader.ElementType);
      if (reader.ElementType == typeof(Sheet)) {
        SheetMeta meta = new();

        foreach (OpenXmlAttribute attr in reader.Attributes) {
          switch (attr.LocalName) {
            case "id":
              meta.Id = GetAttrVal(attr);
              break;
            case "name":
              meta.Name = GetAttrVal(attr);
              break;
          }

          //Console.WriteLine(" - " + attr.LocalName + " " + attr.Value);
        }

        result[curPos] = meta;
        curPos++;
      }
    }

    return result;
  }

  string GetAttrVal(OpenXmlAttribute attr) {
    string? valStr = attr.Value;
    Debug.Assert(!string.IsNullOrEmpty(valStr));
    return valStr;
  }

  record BookRes(SpreadsheetDocument Doc, FileStream Stream) {
    public void Dispose() {
      Doc.Dispose();
      Stream.Dispose();
    }
  }

  record BookImpl(LargeExcelReader Outer, BookRes Res, Dictionary<int, SheetMeta> Sheets) : LargeExcelBook {
    public void Dispose() => Res.Dispose();
    public LargeExcelSheet Sheet(int position) => Outer.BookSheet(this, position);
    public LgeSharedStrTable SharedStringTable() => SSTableMaker.Make(Res.Doc.WorkbookPart!);
  }

  SheetImpl BookSheet(BookImpl book, int pos) {
    SheetMeta meta = book.Sheets[pos];
    return new(book, meta);
  }

  record SheetImpl(BookImpl Book, SheetMeta Meta) : LargeExcelSheet {
    public LgeSheetReader Reader() => Book.Outer.SheetReader(this);
  }

  SheetReaderImpl SheetReader(SheetImpl sheet) {
    SpreadsheetDocument doc = sheet.Book.Res.Doc;
    WorkbookPart bookPart = doc.WorkbookPart!;
    string sheetPartId = sheet.Meta.Id;

    WorksheetPart sheetPart = bookPart.WorksheetParts
      .Where(p => bookPart.GetIdOfPart(p).Equals(sheetPartId))
      .Single();

    OpenXmlReader reader = OpenXmlReader.Create(sheetPart);
    return new(reader);
  }

  readonly ExcelFileUtils _excelUtil = excelUtil;
}

public interface LgeSharedStrTable {
  Task<string> Get(int index);
  Task<string> GetVal(LargeExcelCell cell);
}

public record LgeRowAttr(string Ref);
public interface LgeSheetReader : IDisposable {
  void SkipToRow(int rowNum);
  bool ReadRow();
  LgeRowAttr GetRowAttr();
  bool ReadCell();
  LargeExcelCell ConsumeCell();
}

public record LargeExcelCell(string Ref, string Type, string Value) {
  public string ColLetter(LgeRowAttr rowAttr) {
    int rowNumLen = rowAttr.Ref.Length;
    return Ref[..^rowNumLen];
  }
}
public interface LargeExcelSheet {
  LgeSheetReader Reader();
}
public interface LargeExcelBook : IDisposable {
  LargeExcelSheet Sheet(int position);
  LgeSharedStrTable SharedStringTable();
}
